#include "./romfs.h"
#include "dfs.h"
#include "dfs_inner.h"



/* romfs dfs 接口 */



/* define */
#define TAG "ROMFS-DFS"



/* static func */

static inline int romfs_dfs_check_dirent(struct romfs_dirent *dirent)
{
    if ((dirent->type != ROMFS_DIRENT_FILE && dirent->type != ROMFS_DIRENT_DIR) ||
         dirent->size == ~0)
    {
        return -1;
    }
    return 0;
}

static struct romfs_dirent *romfs_dfs_lookup(struct romfs_dirent *root, const char *path, uint32_t *size)
{
    uint32_t index, found;
    const char *subpath, *subpath_end;
    struct romfs_dirent *dirent;
    uint32_t dirent_size;

    /* Check the root. */
    if (romfs_dfs_check_dirent(root) != 0) return NULL;

    if (path[0] == '/' && path[1] == '\0')
    {
        *size = root->size;
        return root;
    }

    /* goto root directy entries */
    dirent = (struct romfs_dirent *)root->data;
    dirent_size = root->size;

    /* get the end position of this subpath */
    subpath_end = path;
    /* skip /// */
    while (*subpath_end && *subpath_end == '/') subpath_end ++;
    subpath = subpath_end;
    while ((*subpath_end != '/') && *subpath_end) subpath_end ++;

    while (dirent != NULL)
    {
        found = 0;

        /* search in folder */
        for (index = 0; index < dirent_size; index ++)
        {
            if (romfs_dfs_check_dirent(&dirent[index]) != 0) return NULL;

            if ( strlen(dirent[index].name) == (subpath_end - subpath) &&
                 strncmp(dirent[index].name, subpath, (subpath_end - subpath)) == 0)
            {
                dirent_size = dirent[index].size;

                /* skip /// */
                while (*subpath_end && *subpath_end == '/') subpath_end ++;
                subpath = subpath_end;
                while ((*subpath_end != '/') && *subpath_end) subpath_end ++;

                if (!(*subpath))
                {
                    *size = dirent_size;
                    return &dirent[index];
                }

                if (dirent[index].type == ROMFS_DIRENT_DIR)
                {
                    /* enter directory */
                    dirent = (struct romfs_dirent *)dirent[index].data;
                    found = 1;
                    break;
                }
                else
                {
                    /* return file dirent */
                    if (subpath != NULL) break; /* not the end of path */
                    return &dirent[index];
                }
            }
        }

        if (!found) break; /* not found */
    }

    /* not found */
    return NULL;
}




/* static func - file */

static int romfs_dfs_file_open(struct dfs_fd *file)
{
    uint32_t size;
    struct romfs_dirent *dir_root, *dir;
    struct dfs_filesystem *fs;

    if (file == NULL || file->data == NULL)
    {
        LOG_E(TAG, "open file or file->data null");
        return -EINVAL;
    }

    fs = (struct dfs_filesystem*)(file->data);
    dir_root = (struct romfs_dirent*)(fs->data);

    if (romfs_dfs_check_dirent(dir_root) < 0)
    {
        return -EIO;
    }

    if (file->flags & (O_CREAT | O_WRONLY | O_APPEND | O_TRUNC | O_RDWR))
    {
        return -EINVAL;
    }

    dir = romfs_dfs_lookup(dir_root, file->path, &size);
    if (dir == NULL) return -ENOENT;

    /* entry is a directory file type */
    if (dir->type == ROMFS_DIRENT_DIR)
    {
        if (!(file->flags & O_DIRECTORY)) return -ENOENT;
    }
    else
    {
        /* entry is a file, but open it as a directory */
        if (file->flags & O_DIRECTORY) return -ENOENT;
    }

    file->data = dir;
    file->size = size;
    file->pos = 0;

    return 0;
}

static int romfs_dfs_file_close(struct dfs_fd *file)
{
    file->data = NULL;
    return 0;
}

static int romfs_dfs_file_ioctl(struct dfs_fd *file, int cmd, void *args)
{
    return -EIO;
}

static int romfs_dfs_file_read(struct dfs_fd *file, void *buf, size_t count)
{
    uint32_t length;
    struct romfs_dirent *dirent;

    if (file == NULL || file->data == NULL)
    {
        LOG_E(TAG, "read file or file->data null");
        return -EINVAL;
    }

    dirent = (struct romfs_dirent *)file->data;

    if (romfs_dfs_check_dirent(dirent) != 0)
    {
        return -EIO;
    }

    if (count < file->size - file->pos) length = count;
    else length = file->size - file->pos;

    if (length > 0) memcpy(buf, &(dirent->data[file->pos]), length);

    /* update file current position */
    file->pos += length;

    return length;
}

static int romfs_dfs_file_lseek(struct dfs_fd *file, off_t offset)
{
    if (offset <= file->size)
    {
        file->pos = offset;
        return file->pos;
    }
    return -EIO;
}

static int romfs_dfs_file_getdents(struct dfs_fd *file, struct dirent *dirp, uint32_t count)
{
    uint32_t index;
    const char *name;
    struct dirent *d;
    struct romfs_dirent *dirent, *sub_dirent;

    if (file == NULL || file->data == NULL)
    {
        LOG_E(TAG, "getdents file or file->data null");
        return -EINVAL;
    }

    dirent = (struct romfs_dirent *)file->data;
    if (romfs_dfs_check_dirent(dirent) != 0) return -EIO;

    // RT_ASSERT(dirent->type == ROMFS_DIRENT_DIR);

    /* enter directory */
    dirent = (struct romfs_dirent *)dirent->data;

    /* make integer count */
    count = (count / sizeof(struct dirent));
    if (count == 0) return -EINVAL;

    index = 0;
    for (index = 0; index < count && file->pos < file->size; index ++)
    {
        d = dirp + index;

        sub_dirent = &dirent[file->pos];
        name = sub_dirent->name;

        /* fill dirent */
        if (sub_dirent->type == ROMFS_DIRENT_DIR) d->d_type = 0x02; // dir
        else d->d_type = 0x01; // reg

        d->d_namlen = strlen(name);
        d->d_reclen = (uint16_t)sizeof(struct dirent);
        strncpy(d->d_name, name, strlen(name) + 1);

        /* move to next position */
        ++ file->pos;
    }

    return index * sizeof(struct dirent);
}

/* static func - filesystem */

static int romfs_dfs_fs_mount(struct dfs_filesystem *fs, unsigned long rwflag, const void *data)
{
    struct romfs_dirent *root_dirent;

    if (data == NULL) return -EIO;

    root_dirent = (struct romfs_dirent *)data;
    fs->data = root_dirent;

    return 0;
}

static int romfs_dfs_fs_unmount(struct dfs_filesystem *fs)
{
    return 0;
}

static int romfs_dfs_fs_stat(struct dfs_filesystem *fs, const char *path, struct stat *st)
{
    uint32_t size;
    struct romfs_dirent *dirent;
    struct romfs_dirent *root_dirent;

    root_dirent = (struct romfs_dirent *)fs->data;
    dirent = romfs_dfs_lookup(root_dirent, path, &size);

    if (dirent == NULL) return -ENOENT;

    st->st_dev = 0;
    st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH |
                  S_IWUSR | S_IWGRP | S_IWOTH;

    if (dirent->type == ROMFS_DIRENT_DIR)
    {
        st->st_mode &= ~S_IFREG;
        st->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
    }

    st->st_size = dirent->size;
    st->st_mtime = 0;

    return 0;
}

/* export func */



static const struct dfs_file_ops romfs_dfs_file_ops = 
{
    .open = romfs_dfs_file_open,
    .close = romfs_dfs_file_close,
    .ioctl = romfs_dfs_file_ioctl,
    .read = romfs_dfs_file_read,
    .write = NULL,
    .flush = NULL,
    .lseek = romfs_dfs_file_lseek,
    .getdents = romfs_dfs_file_getdents,
};

static const struct dfs_filesystem_ops romfs_dfs_fs_ops = 
{
    .name = "rom",
    .flags = DFS_FS_FLAG_DEFAULT,
    .fops = &romfs_dfs_file_ops,

    .mount = romfs_dfs_fs_mount,
    .unmount = romfs_dfs_fs_unmount,
    .mkfs = NULL,
    .statfs = NULL,
    .unlink = NULL,
    .stat = romfs_dfs_fs_stat,
    .rename = NULL,
};


int romfs_dfs_register(void)
{
    int sta;

    sta = dfs_fs_register(&romfs_dfs_fs_ops);
    if (sta == 0)
    {
        // LOG_I(TAG, "filesystem '%s' register OK", romfs_dfs_fs_ops.name);
    }
    else
    {
        LOG_E(TAG, "filesystem '%s' register fail", romfs_dfs_fs_ops.name);
    }
    return 0;
}



